package org.jflame.commons.valid;

import org.jflame.commons.net.IPAddressHelper;
import org.jflame.commons.util.ChineseHelper;
import org.jflame.commons.util.ChineseIDCard;
import org.jflame.commons.util.MapHelper;
import org.jflame.commons.util.StringHelper;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;

/**
 * 常用验证方法.支持规则:
 * <ul>
 * <li>正则表达式</li>
 * <li>手机(国内)</li>
 * <li>电话号(国内)</li>
 * <li>中国身份证(15或18位)</li>
 * <li>邮箱</li>
 * <li>是否含特殊字符：*%#\=&lt;&gt;`';?&amp;!</li>
 * <li>字符串长度</li>
 * <li>是否是字母或数字或下划线</li>
 * <li>是否是数字</li>
 * <li>是否是字母</li>
 * <li>JSR303验证器验证实体Bean对象</li>
 * <li>IP地址(ipv4 or v6)</li>
 * <li>空或非空判断(支持类型:字符串,集合,数组,map)</li>
 * </ul>
 *
 * @author zyc
 */
public final class Validators {

    /**
     * 手机号正则(国内)
     */
    public static final LazyPattern REGEX_MOBLIE = new LazyPattern("^((\\+86)|(86))?0{0,1}1[3-9]\\d{9}$");
    /**
     * 电话号正则(国内)
     */
    public static final LazyPattern REGEX_TEL = new LazyPattern("^(0\\d{2,3}-?){0,1}\\d{7,9}$");
    /**
     * 邮箱正则
     */
    public static final LazyPattern REGEX_MAIL = new LazyPattern("^[a-zA-Z0-9_\\.]+@[a-zA-Z0-9-]+[\\.a-zA-Z]+$");
    /**
     * sql特殊字符
     */
    public final static char[] UNSAFE_SQLCHARS = { '*','%','\'','=','<','>','`',';','?','&','!','#' };

    /**
     * 检验指定字符串是否符合指定的正则表达式
     * 
     * @param pattern 正则表达式对象
     * @param str 待验证字符串
     * @return
     */
    public static boolean isMatch(Pattern pattern, String str) {
        if (str == null || pattern == null) {
            return false;
        }

        return pattern.matcher(str)
                .matches();
    }

    /**
     * 检验指定字符串是否符合指定的正则表达式
     *
     * @param str 待验证值
     * @param pattern 正则
     * @return 匹配返回true
     */
    public static boolean regex(String str, String pattern) {
        if (str == null || pattern == null) {
            return false;
        }
        return Pattern.matches(pattern, str);
    }

    /**
     * 检验指定字符串是否符合指定的正则表达式
     * 
     * @param str 待验证值
     * @param pattern 正则
     * @param isCachePattern 是否缓存正则表达式
     * @return
     */
    public static boolean regex(String str, String pattern, boolean isCachePattern) {
        if (str == null || pattern == null) {
            return false;
        }
        return isCachePattern ? isMatch(RegexPool.pattern(pattern), str) : Pattern.matches(pattern, str);
    }

    /**
     * 检测指定字符串中是否有符合给定正则表达式的部分
     *
     * @param value
     * @param regex
     * @return
     */
    public static boolean find(String value, String regex) {
        if (value == null) {
            return false;
        }
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(value);
        return m.find();
    }

    /**
     * 验证手机或电话号格式
     *
     * @param contact 待验证字符串
     * @param type 验证类型:0手机格式,1电话格式,2手机或电话格式
     * @return
     */
    public static boolean isMobileOrTel(String contact, int type) {
        boolean result = false;

        switch (type) {
            case 0:
                result = REGEX_MOBLIE.isMatch(contact);
                break;
            case 1:
                result = REGEX_TEL.isMatch(contact);
                break;
            case 2:
                result = REGEX_MOBLIE.isMatch(contact) || REGEX_TEL.isMatch(contact);
                break;
            default:
                break;
        }
        return result;
    }

    /**
     * 验证手机号格式
     *
     * @param mobile 手机号
     * @return
     */
    public static boolean isMobile(final String mobile) {
        return isMobileOrTel(mobile, 0);
    }

    /**
     * 验证邮箱格式
     *
     * @param mail 邮箱
     * @return
     */
    public static boolean isEmail(final String mail) {
        return REGEX_MAIL.isMatch(mail);
    }

    /**
     * 判断是否是汉字
     *
     * @param ch 字符
     * @return
     */
    public static boolean isChinese(char ch) {
        return ChineseHelper.isChinese(ch);
    }

    /**
     * 判断是否是数字,包括:16进制,小数,负数
     *
     * @param str
     * @return
     */
    public static boolean isNumber(String str) {
        boolean isOk = NumberUtils.isCreatable(str);
        // .点号开头不算正确的数字
        if (isOk && str.charAt(0) == '.') {
            isOk = false;
        }
        return isOk;
    }

    /**
     * 判断是否是整数(不带正负符号,不带小数点,不能以多个0开头)
     *
     * @param str
     * @return
     */
    public static boolean isInteger(String str) {
        boolean isOk = NumberUtils.isDigits(str);
        // 超过2个0开头的不算正确整数
        if (isOk && str.startsWith("00")) {
            isOk = false;
        }
        return isOk;
    }

    /**
     * 检查是否包含可能引起sql注入的字符*%#\=&lt;&gt;`';?&amp;!
     *
     * @param str
     * @return 不包含返回true
     */
    public static boolean safeChar(String str) {
        return !StringUtils.containsAny(str, UNSAFE_SQLCHARS);
    }

    /**
     * 验证字符串长度是否大于或等于minLength
     *
     * @param str 字符串
     * @param minLength 最小长度
     * @return
     */
    public static boolean minLength(String str, int minLength) {
        return stringLength(str, minLength, -1);
    }

    /**
     * 验证字符串长度是否小于或等于maxLength
     *
     * @param str 字符串
     * @param maxLength 最大长度
     * @return
     */
    public static boolean maxLength(String str, int maxLength) {
        return stringLength(str, -1, maxLength);
    }

    /**
     * 验证字符串长度
     *
     * @param str
     * @param min 最小长度,传入0或负数表示不计算
     * @param max 最大长度,传入0或负数表示不计算
     * @return
     */
    public static boolean stringLength(String str, int min, int max) {
        if (str == null) {
            return false;
        }
        if (min <= 0 && max <= 0) {
            throw new IllegalArgumentException("参数min,max不能同时小于0");
        }
        if (max > 0 && min > max) {
            throw new IllegalArgumentException("参数min应小于max");
        }
        if (min <= 0 && max > 0) {
            return str.length() <= max;
        } else if (min > 0 && max <= 0) {
            return str.length() >= min;
        } else if (min > 0 && max > 0) {
            int len = str.length();
            return len >= min && len <= max;
        }
        return false;
    }

    /**
     * 判断字符串是否字母数字或下划线组成
     *
     * @return
     */
    public static boolean isLetterOrNumOrUnderline(String str) {
        return regex(str, "^\\w+$");
    }

    /**
     * 判断只有字母
     *
     * @param str
     * @return
     */
    public static boolean isLetter(String str) {
        return StringUtils.isAlpha(str);
    }

    /**
     * 判断是否是ip地址字符串,ip v4 or v6
     *
     * @param ip
     * @return
     */
    public static boolean isIPAddress(String ip) {
        return IPAddressHelper.isIP(ip);
    }

    /**
     * 使用JSR303验证器验证实体对象
     *
     * @param entity 待验证的实体对象
     * @param groups 规则分组
     * @return 错误信息Map, key=属性名 value=错误描述
     */
    public static <T> Map<String,String> validBean(T entity, Class<?>... groups) {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        Set<ConstraintViolation<T>> errors = validator.validate(entity, groups);
        Iterator<ConstraintViolation<T>> it = errors.iterator();
        Map<String,String> errorMap = new HashMap<>();
        while (it.hasNext()) {
            ConstraintViolation<T> cv = (ConstraintViolation<T>) it.next();
            errorMap.put(cv.getPropertyPath()
                    .toString(), cv.getMessage());
        }
        return errorMap;
    }

    /**
     * 提取ConstraintViolationException异常中的验证错误信息,合并为字符串返回
     * 
     * @param cve ConstraintViolationException异常
     * @return 返回合并后的验证错误信息
     */
    public static String extract(ConstraintViolationException cve) {
        return cve.getConstraintViolations()
                .stream()
                .map(jakarta.validation.ConstraintViolation::getMessage)
                .collect(Collectors.joining(";"));
    }

    /**
     * 判断是否是有效的身份证
     *
     * @param idcard
     * @return true=是
     */
    public static boolean isIDCard(String idcard) {
        return ChineseIDCard.isValid(idcard);
    }

    /**
     * 判断集合为空,等于null或元素为0个
     *
     * @param collection 集合
     * @return true=空
     */
    public static <E> boolean isEmpty(Collection<E> collection) {
        return collection == null || collection.isEmpty();
    }

    /**
     * 判断集合不为空,不等于null且元素&gt;0个
     *
     * @param collection 集合
     * @return true=非空
     */
    public static <E> boolean isNotEmpty(Collection<E> collection) {
        return !isEmpty(collection);
    }

    /**
     * 判断数组为空,等于null或元素为0个
     *
     * @param array 数组
     * @return true=空
     */
    public static <E> boolean isEmpty(E[] array) {
        return array == null || array.length == 0;
    }

    /**
     * 判断数组不为空,不等于null且元素&gt;0个
     *
     * @param array 数组
     * @return true=非空
     */
    public static <E> boolean isNotEmpty(E[] array) {
        return array != null && array.length > 0;
    }

    /**
     * 判断Map为空,等于null或元素为0个
     *
     * @param map Map
     * @return true=空
     */
    public static boolean isEmpty(Map<?,?> map) {
        return MapHelper.isEmpty(map);
    }

    /**
     * 判断Map不为空,不等于null且元素&gt;0个
     *
     * @param map Map
     * @return true=非空
     */
    public static boolean isNotEmpty(Map<?,?> map) {
        return MapHelper.isNotEmpty(map);
    }

    /**
     * 判断字符串为空,等于null或空白字符
     *
     * @param str 字符串
     * @return true=空
     */
    public static boolean isEmpty(CharSequence str) {
        return StringHelper.isEmpty(str);
    }

    /**
     * 判断字符串不为空,不等于null和空白字符
     *
     * @param str 字符串
     * @return true=非空
     */
    public static boolean isNotEmpty(CharSequence str) {
        return StringHelper.isNotEmpty(str);
    }

    /**
     * 断言非空,如果参数obj==null抛出IllegalArgumentException
     *
     * @param obj 任意对象
     * @param message 异常信息
     */
    public static void requireNonNull(Object obj, String message) {
        if (obj == null) {
            throw new IllegalArgumentException(message);
        }
    }

}
